package com.hotmocha.jclient;

import com.hotmocha.jclient.codec.ResponseCodecFactory;
import com.hotmocha.jclient.comm.R;
import com.hotmocha.jclient.model.net.*;
import com.hotmocha.jclient.net.MessageReceiveHandler;
import org.apache.log4j.Logger;
import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.filter.logging.MdcInjectionFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ChatClientMain extends JFrame {
    private static final long serialVersionUID = 1538675161745436968L;
    private JTextField inputText;

    private FileWriter writer;

    private JButton loginButton;
    private JButton quitButton;
    private JButton closeButton;

    private JTextField serverField;
    private String serverFieldValue = "127.0.0.1";

    private JTextField portField;
    private String portFiledValue = "15210";

    private JTextField nameField;
    private String nameFieldValue = "jolin";

    private JTextField useridField;
    private String useridFieldValue = "90";

    private JTextArea area;
    private JScrollPane scroll;

    private static final int LOGIN_SEND = 1;
    private static final int LOGIN_OK = 2;
    private static final int GET_USER_LIST = 3;
    
    private int stage = 0;
    private MessageReceiveHandler handler;
    private NioSocketConnector connector;
    private ActionsManager manager;
    private IoSession session;
    private ClientInfo clientInfo;
    private MemberList memberList;
    private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    private void initConnector() {
        connector = new NioSocketConnector();
        IoFilter LOGGING_FILTER = new LoggingFilter();
        connector.getFilterChain().addLast("mdc", new MdcInjectionFilter());
        connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ResponseCodecFactory()));
        connector.getFilterChain().addLast("logger", LOGGING_FILTER);
        this.manager = new ActionsManager(this);
        this.handler = new MessageReceiveHandler(this.manager);
        TkMessage.addMessageType(R.MSG_TYPE_CLIENT_INFO, ClientInfo.class);
        TkMessage.addMessageType(R.MSG_TYPE_COMM_MSG, Message.class);
        TkMessage.addMessageType(R.MSG_TYPE_MEMBER_LIST, MemberList.class);

        connector.setHandler(this.handler);
    }

    public ChatClientMain() {
        super("Chat Client based on Apache MINA");

        loginButton = new JButton(new AbstractAction (){
            private static final long serialVersionUID = 3596719854773863244L;

            public void actionPerformed(ActionEvent e) {
                SocketAddress address = new InetSocketAddress(serverField.getText(), Integer.valueOf(portField.getText()));
                if (connect(address) == false) {
                    JOptionPane.showMessageDialog(ChatClientMain.this, "connect failed");
                    return;
                }

                if (login(useridField.getText(), nameField.getText()) == false) {
                    JOptionPane.showMessageDialog(ChatClientMain.this, "login failed");
                    return;
                }
            }
        });
        loginButton.setText("Connect");
        quitButton = new JButton(new LogoutAction());
        quitButton.setText("Disconnect");
        closeButton = new JButton(new QuitAction());
        closeButton.setText("Quit");
        inputText = new JTextField(30);
        inputText.setAction(new BroadcastAction());
        area = new JTextArea(30, 50);
        area.setLineWrap(true);
        scroll = new JScrollPane(this.area);
        nameField = new JTextField(10);
        nameField.setEditable(true);
        nameField.setText(this.nameFieldValue);
        useridField = new JTextField(10);
        useridField.setEditable(true);
        portField = new JTextField(10);
        portField.setEditable(false);
        portField.setText(this.portFiledValue);
        useridField.setText(this.useridFieldValue);
        serverField = new JTextField(10);
        serverField.setText(this.serverFieldValue);
        serverField.setEditable(true);

        JPanel h = new JPanel();
        h.setLayout(new BoxLayout(h, BoxLayout.LINE_AXIS));
        h.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        JLabel idLabel = new JLabel("Id: ");
        JLabel nameLabel = new JLabel("Name: ");
        JLabel serverLabel = new JLabel("Server: ");
        JLabel portLabel = new JLabel("Port: ");
        h.add(nameLabel);
        h.add(Box.createRigidArea(new Dimension(10, 0)));
        h.add(nameField);
        h.add(Box.createRigidArea(new Dimension(10, 0)));
        h.add(idLabel);
        h.add(useridField);
        h.add(Box.createRigidArea(new Dimension(10, 0)));
        h.add(portLabel);
        h.add(portField);
        h.add(Box.createHorizontalGlue());
        h.add(Box.createRigidArea(new Dimension(10, 0)));
        h.add(serverLabel);
        h.add(Box.createRigidArea(new Dimension(10, 0)));
        h.add(serverField);

        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
        p.setBorder(new EmptyBorder(10, 10, 10, 10));

        JPanel left = new JPanel();
        left.setLayout(new BoxLayout(left, BoxLayout.PAGE_AXIS));
        left.add(this.scroll);
        left.add(Box.createRigidArea(new Dimension(0, 5)));
        left.add(Box.createHorizontalGlue());
        left.add(inputText);

        JPanel right = new JPanel();
        right.setLayout(new BoxLayout(right, BoxLayout.PAGE_AXIS));
        right.add(loginButton);
        right.add(Box.createRigidArea(new Dimension(0, 5)));
        right.add(quitButton);
        right.add(Box.createHorizontalGlue());
        right.add(Box.createRigidArea(new Dimension(0, 25)));
        right.add(closeButton);

        p.add(left);
        p.add(Box.createRigidArea(new Dimension(10, 0)));
        p.add(right);

        getContentPane().add(h, BorderLayout.NORTH);
        getContentPane().add(p);

        closeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                quit();
                dispose();
            }
        });
        setLoggedOut();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public void quit() {
        this.dispose();
        connector.dispose();
    }

    public boolean connect(SocketAddress address) {
        try {
            connector.setConnectTimeoutMillis(3000);
            connector.setHandler(handler);
            ConnectFuture future1 = connector.connect(address);
            future1.awaitUninterruptibly();
            if (!future1.isConnected()) {
                return false;
            }
            session = future1.getSession();

            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public boolean login(String userid, String name) {
        this.clientInfo = new ClientInfo();
        this.clientInfo.clientid = Integer.valueOf(userid);
        this.clientInfo.clientname = name;
        this.clientInfo.ip = "";
        TkMessage tkMessage = new TkMessage(this.clientInfo);

        WriteFuture future = session.write(tkMessage);
        future.awaitUninterruptibly();
        if(future.isWritten()) {
        	this.stage = ChatClientMain.LOGIN_SEND;
            return true;
        }
        else {
            System.out.println("login error");
            return false;
        }
    }

    public boolean broadcast(String msg) {
        if (msg.length() > R.MAXMESSAGELEN) {
            JOptionPane.showMessageDialog(ChatClientMain.this, "broadcast error");
            return false;
        }

        Message message = new Message();
        message.clientid = this.clientInfo.clientid;
        message.clientname = this.clientInfo.clientname;
        message.content = msg;
        //message.contentlen = msg.length();
        TkMessage tkMessage = new TkMessage(message);
        
        WriteFuture future = session.write(tkMessage);
        future.awaitUninterruptibly();
        if( future.isWritten() ) {
        	String dateStr = this.df.format(new Date());
            String tmp = "\t\t\tME@" + dateStr + "\n\t\t\t" + message.content + "\n";
            this.append(tmp);
            this.appendContentToLog(tmp);

            return true;
        }
        else {
            JOptionPane.showMessageDialog(ChatClientMain.this, "send error");
            return false;
        }
    }

    public boolean getUserList(MemberList memberList) {
        if (memberList.cmd == R.MEMBER_CMD_INIT) {
            this.memberList = memberList;
        }

        if (memberList.cmd == R.MEMBER_CMD_INIT) {
            String tmp = "init user list\n";
            for (int i = 0; i < memberList.clientinfo.size(); i++) {
                ClientInfo clientInfo = memberList.clientinfo.get(i);
                tmp += "ID:" + clientInfo.clientid +
                        " Name:" + clientInfo.clientname +
                        " IP:" + clientInfo.ip + "\n";
            }
            this.append(tmp);
            
            this.stage =  ChatClientMain.GET_USER_LIST;

            appendContentToLog(tmp);
        }
        else if (memberList.cmd == R.MEMBER_CMD_DELETE) {
            String tmp = "leave user\n";
            for (int i = 0; i < memberList.clientinfo.size(); i++) {
                ClientInfo clientInfo = memberList.clientinfo.get(i);
                tmp += "ID:" + clientInfo.clientid +
                        " Name?:" + clientInfo.clientname +
                        " IP:" + clientInfo.ip + "\n";

                for (int j = 0; j < this.memberList.clientinfo.size(); j++) {
                    ClientInfo clientInfo2 = this.memberList.clientinfo.get(j);
                    if (clientInfo.clientid == clientInfo2.clientid) {
                        this.memberList.clientinfo.remove(j);
                    }
                }
            }

            this.append(tmp);
        }
        else {
            String tmp = "add user\n";
            for (int i = 0; i < memberList.clientinfo.size(); i++) {
                ClientInfo clientInfo = memberList.clientinfo.get(i);
                tmp += "ID:" + clientInfo.clientid +
                        " Name:" + clientInfo.clientname +
                        " IP:" + clientInfo.ip + "\n";

                boolean found = false;
                for (int j = 0; j < this.memberList.clientinfo.size(); j++) {
                    ClientInfo clientInfo2 = this.memberList.clientinfo.get(j);
                    if (clientInfo.clientid == clientInfo2.clientid) {
                        found = true;
                    }
                }
                if (found == false) {
                    this.memberList.clientinfo.add(clientInfo);
                }

                this.append(tmp);
            }
        }

        return true;
    }

    public boolean getChatMessage(Message message) {
    	String dateStr = this.df.format(new Date());
    this.append(message.clientid + "(" + message.clientname + "@" + dateStr + ")\n");
        this.append(message.content + "\n");
        return true;
    }

    public boolean getRespStatus(RespStatus respStatus) {
    	if (respStatus.status != 0) {
            JOptionPane.showMessageDialog(ChatClientMain.this, respStatus.errormsg);
            quit();
    	}
    	else {
    	}

        return true;
    }
    
    private class BroadcastAction extends AbstractAction {

        private static final long serialVersionUID = -6276019615521905411L;

        public void actionPerformed(ActionEvent e) {
            broadcast(inputText.getText());
            inputText.setText("");
        }
    }

    private class LogoutAction extends AbstractAction {
        private static final long serialVersionUID = 1655297424639924560L;

        public void actionPerformed(ActionEvent e) {
            try {
                quit();
            } catch (Exception e1) {
                JOptionPane.showMessageDialog(ChatClientMain.this,
                        "Session could not be closed.");
            }
        }
    }

    private class QuitAction extends AbstractAction {
        private static final long serialVersionUID = -6389802816912005370L;

        public void actionPerformed(ActionEvent e) {
            quit();
            try {
                writer.close();
            } catch (Exception ignored) {

            }
            ChatClientMain.this.dispose();
        }
    }

    private void setLoggedOut() {
        inputText.setEnabled(false);
        quitButton.setEnabled(false);
        loginButton.setEnabled(true);
    }

    private void setLoggedIn() {
        area.setText("");
        inputText.setEnabled(true);
        quitButton.setEnabled(true);
        loginButton.setEnabled(false);
    }

    private void append(String text) {
        area.append(text);
        this.area.setCaretPosition(this.area.getText().length());
    }

    private void notifyError(String message) {
        JOptionPane.showMessageDialog(this, message);
    }

    private SocketAddress parseSocketAddress(String s) {
        s = s.trim();
        int colonIndex = s.indexOf(":");
        if (colonIndex > 0) {
            String host = s.substring(0, colonIndex);
            int port = parsePort(s.substring(colonIndex + 1));
            return new InetSocketAddress(host, port);
        } else {
            int port = parsePort(s.substring(colonIndex + 1));
            return new InetSocketAddress(port);
        }
    }

    private int parsePort(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException nfe) {
            throw new IllegalArgumentException("Illegal port number: " + s);
        }
    }

    public void connected() {
        setLoggedIn();
        System.out.println("connected");
    }

    public void disconnected() {
        append("Connection closed.\n");
        setLoggedOut();
    }

    public void error(String message) {
        notifyError(message + "\n");
    }


    public NioSocketConnector getConnector() {
        return connector;
    }

    public void setConnector(NioSocketConnector connector) {
        this.connector = connector;
    }

    public IoSession getSession() {
        return session;
    }

    public void setSession(IoSession session) {
        this.session = session;
    }

    private boolean initLogFile() {
        try {
            //打开一个写文件器，构造函数中的第二个参数true表示以追加形式写文件
            writer = new FileWriter("tkclient.log", true);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    private boolean appendContentToLog(String content) {
        try {
            writer.write(content);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    public static void main(String[] args) {
        Logger logger  =  Logger.getLogger(ChatClientMain.class);

        ChatClientMain client = new ChatClientMain();
//        int w = (Toolkit.getDefaultToolkit().getScreenSize().width -client.getWidth()) / 2;
//        int h = (Toolkit.getDefaultToolkit().getScreenSize().height -client.getHeight()) / 2;
        client.setLocation(200, 200);
        
        client.initConnector();
        client.initLogFile();
        client.pack();
        client.setVisible(true);
    }
}
